//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

public import Foundation
public import LanguageServerProtocol

/// The BSP server can inform the client on the execution state of any task in the build tool.
/// The execution of some tasks, such as compilation or tests, must always be reported by the server.
///
/// The server may also send additional task notifications for actions not covered by the protocol, such as resolution
/// or packaging. BSP clients can then display this information to their users at their discretion.
///
/// When beginning a task, the server may send `build/taskStart`, intermediate updates may be sent in
/// `build/taskProgress`.
///
/// If a `build/taskStart` notification has been sent, the server must send build/taskFinish on completion of the same
/// task.
///
/// `build/taskStart`, `build/taskProgress` and `build/taskFinish` notifications for the same task must use the same
/// `taskId`.
///
/// Tasks that are spawned by another task should reference the originating task's `taskId` in their own `taskId`'s
/// `parent` field. Tasks spawned directly by a request should reference the request's `originId` parent.
public struct TaskStartNotification: NotificationType {
  public static let method: String = "build/taskStart"

  /// Unique id of the task with optional reference to parent task id
  public var taskId: TaskId

  /// A unique identifier generated by the client to identify this request.
  public var originId: String?

  /// Timestamp of when the event started in milliseconds since Epoch.
  @CustomCodable<MillisecondsSince1970Date?>
  public var eventTime: Date?

  /// Message describing the task.
  public var message: String?

  /** Kind of data to expect in the `data` field. If this field is not set, the kind of data is not specified. */
  public var dataKind: TaskStartDataKind?

  /// Optional metadata about the task.
  // Objects for specific tasks like compile, test, etc are specified in the protocol.
  public var data: LSPAny?

  public init(
    taskId: TaskId,
    originId: String? = nil,
    eventTime: Date? = nil,
    message: String? = nil,
    dataKind: TaskStartDataKind? = nil,
    data: LSPAny? = nil
  ) {
    self.taskId = taskId
    self.originId = originId
    self.eventTime = eventTime
    self.message = message
    self.dataKind = dataKind
    self.data = data
  }
}

/// Task start notifications may contain an arbitrary interface in their `data` field.
///
/// The kind of interface that is contained in a notification must be specified in the `dataKind` field.
public struct TaskStartDataKind: RawRepresentable, Codable, Hashable, Sendable {
  public let rawValue: String
  public init(rawValue: String) {
    self.rawValue = rawValue
  }

  /// `data` field must contain a `CompileTaskData` object.
  public static let compileTask = TaskStartDataKind(rawValue: "compile-task")

  /// `data` field must contain a `TestStart` object.
  public static let testStart = TaskStartDataKind(rawValue: "test-start")

  /// `data` field must contain a `TestTask` object.
  public static let testTask = TaskStartDataKind(rawValue: "test-task")
}

/// This structure is embedded in the `TaskStartNotification.data` field, when the `dataKind` field contains
/// "compile-task".
///
/// The beginning of a compilation unit may be signalled to the client with a `build/taskStart` notification.
///
/// When the compilation unit is a build target, the notification's `dataKind` field must be "compile-task" and the
/// `data` field must include a `CompileTaskData` object
public struct CompileTaskData: Codable, Hashable, Sendable {
  public var target: BuildTargetIdentifier

  public init(target: BuildTargetIdentifier) {
    self.target = target
  }
}

/// This structure is embedded in the `TaskStartNotification.data` field, when the `dataKind` field contains
/// "test-start".
public struct TestStartData: Codable, Hashable, Sendable {
  /// Name or description of the test.
  public var displayName: String

  /// Source location of the test, as LSP location.
  public var location: Location?

  public init(displayName: String, location: Location? = nil) {
    self.displayName = displayName
    self.location = location
  }
}

/// This structure is embedded in the `TaskStartNotification.data` field, when the `dataKind` field contains
/// "test-task".
///
/// The beginning of a testing unit may be signalled to the client with a `build/taskStart`` notification.
/// When the testing unit is a build target, the notification's `dataKind` field must be `test-task` and the `data`
/// field must include a `TestTaskData` object.
public struct TestTaskData: Codable, Hashable, Sendable {
  public var target: BuildTargetIdentifier

  public init(target: BuildTargetIdentifier) {
    self.target = target
  }
}

/// If `data` contains a string value for the `workDoneProgressTitle` key, then the task's message will be displayed in
/// the client as a work done progress with that title.
public struct WorkDoneProgressTask: LSPAnyCodable {
  /// The title with which the work done progress should be created in the client.
  public let title: String

  public init(title: String) {
    self.title = title
  }

  public init?(fromLSPDictionary dictionary: [String: LanguageServerProtocol.LSPAny]) {
    guard case .string(let title) = dictionary["workDoneProgressTitle"] else {
      return nil
    }
    self.title = title
  }

  public func encodeToLSPAny() -> LanguageServerProtocol.LSPAny {
    return .dictionary([
      "workDoneProgressTitle": .string(title)
    ])
  }
}
